package tt.dz.service.imp.middleware;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import tt.dz.dao.ext.charger.OmsChargerRateDao;
import tt.dz.entity.charger.OmsChargerRate;
import tt.dz.service.imp.sys.PaymentDetail;
import tt.dz.util.OmsConstant;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@Component
public class PriceService
{
	@Resource
	OmsChargerRateDao omsChargerRateDao;

	private Log log = LogFactory.getLog(PriceService.class);

	//	费用类型:1：预约费用；2：充电费用；3：充电服务费,0为尚未分配类型的费用
	public final static int FEE_TYPE_UNDEFINED = 0;
	public final static int FEE_TYPE_RESERVATION = 1;
	public final static int FEE_TYPE_CHARGING = 2;
	public final static int FEE_TYPE_CHARGE_SERVICE = 3;

	private final static BigDecimal default_fee = BigDecimal.ZERO;

	//	收费模式：0：按时间收费，1：按次数收费

	public BigDecimal computeFee(Integer schemaNo, Date start_time, Date end_time, List<PaymentDetail> paymentDetails)
	{
		log.info(schemaNo);
		log.info(start_time);
		log.info(end_time);

		List<OmsChargerRate> list_vo = omsChargerRateDao.findListByCondition(new OmsChargerRate(), " WHERE rate_schema = '" + schemaNo + "' ");
		if (list_vo == null || list_vo.isEmpty())
			return default_fee;
		int mode = list_vo.get(0).getCharge_mode();
		if (mode == OmsConstant.PaymentMode.ByFrequency)
		{
			BigDecimal fee = list_vo.get(0).getRate().setScale(2, BigDecimal.ROUND_HALF_UP);
			PaymentDetail detail = new PaymentDetail(fee.floatValue(), fee.floatValue(), 1, start_time, end_time, OmsConstant.PaymentMode.ByFrequency);
			paymentDetails.add(detail);
			return fee;
		}
		else if (mode == OmsConstant.PaymentMode.ByTime)
		{
			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
			if (end_time.before(start_time))
				return BigDecimal.ZERO;

			if (!sdf.format(start_time).equals(sdf.format(end_time)))
			{
				Calendar c = Calendar.getInstance();
				c.setTime(start_time);
				c.set(Calendar.HOUR_OF_DAY, 23);
				c.set(Calendar.MINUTE, 59);
				c.set(Calendar.SECOND, 59);
				Date middle_end = c.getTime();
				c.add(Calendar.SECOND, 1);
				Date middle_start = c.getTime();

				BigDecimal part1 = computeFee(schemaNo, start_time, middle_end, paymentDetails);
				BigDecimal part2 = computeFee(schemaNo, middle_start, end_time, paymentDetails);
				return part1.add(part2);
			}

			float total = 0;
			for (OmsChargerRate vo : list_vo)
			{
				float rate = 0;
				if (vo.getRate() != null)
					rate = vo.getRate().floatValue();
				//				long intersect_minute = computeTimeIntersect(vo.getStart_time(), vo.getEnd_time(), start_time, end_time);
				TimeInterval interval = computeTimeIntersect(vo.getStart_time(), vo.getEnd_time(), start_time, end_time);
				long intersect_minute = 0;
				if (interval != null)
				{
					intersect_minute = (interval.end.getTime() - interval.start.getTime()) / (1000 * 60);
					float fee = intersect_minute * rate;
					total += fee;
					PaymentDetail detail = new PaymentDetail(fee, rate, intersect_minute, interval.start, interval.end, OmsConstant.PaymentMode.ByTime);
					paymentDetails.add(detail);
				}

			}
			log.info(total);
			return new BigDecimal(total).setScale(2, BigDecimal.ROUND_HALF_UP);
		}
		else
			return default_fee;
	}

	private List<RateVO> getRatesByTimeRange(Integer schemaNo, Date start_time, Date end_time)
	{
		log.info(schemaNo);
		log.info(start_time);
		log.info(end_time);

		List<RateVO> list_rate = new ArrayList<>();
		if (!end_time.before(start_time))
		{
			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
			if (!sdf.format(start_time).equals(sdf.format(end_time)))
			{
				Calendar c = Calendar.getInstance();
				c.setTime(start_time);
				c.set(Calendar.HOUR_OF_DAY, 23);
				c.set(Calendar.MINUTE, 59);
				c.set(Calendar.SECOND, 59);
				Date middle_end = c.getTime();
				c.add(Calendar.SECOND, 1);
				Date middle_start = c.getTime();

				list_rate = getRatesByTimeRange(schemaNo, start_time, middle_end);
				list_rate.addAll(getRatesByTimeRange(schemaNo, middle_start, end_time));
			}
			else
			{

				List<OmsChargerRate> list_vo = omsChargerRateDao.findListByCondition(new OmsChargerRate(), String.format(" WHERE rate_schema = %1$d ORDER BY start_time ", schemaNo));
				Calendar statr_time_without_date = convertWithoutDate(start_time);
				Calendar end_time_without_date = convertWithoutDate(end_time);
				for (int i = 0; i < list_vo.size(); i++)
				{
					OmsChargerRate vo = list_vo.get(i);
					Calendar s_without_date = convertWithoutDate(vo.getStart_time());
					Calendar e_without_date = convertWithoutDate(vo.getEnd_time());
					if (s_without_date.before(statr_time_without_date) && e_without_date.before(statr_time_without_date))
						continue;
					if (s_without_date.after(end_time_without_date) && e_without_date.after(end_time_without_date))
						continue;

					float rate = 0;
					if (vo.getRate() != null)
						rate = vo.getRate().floatValue();
					RateVO rateVO = new RateVO(rate, s_without_date.getTime(), e_without_date.getTime());
					list_rate.add(rateVO);
				}
			}
		}

		return list_rate;

	}

	public BigDecimal computeChargingFeeRoughly(Integer schemaNo, Date start_time, Date end_time, float chargingAmount)
	{
		log.info(schemaNo);
		log.info(start_time);
		log.info(end_time);
		log.info(chargingAmount);

		if (chargingAmount == 0)
			return default_fee;

		List<OmsChargerRate> list_vo = omsChargerRateDao.findListByCondition(new OmsChargerRate(), String.format(" WHERE rate_schema = %1$d ORDER BY start_time ", schemaNo));
		if (list_vo == null || list_vo.isEmpty())
			return default_fee;
		int mode = list_vo.get(0).getCharge_mode();
		if (mode == OmsConstant.PaymentMode.ByFrequency)
		{
			return list_vo.get(0).getRate().setScale(2, BigDecimal.ROUND_HALF_UP);
		}
		else if (mode == OmsConstant.PaymentMode.ByTime)
		{
			if (end_time.before(start_time))
				return BigDecimal.ZERO;

			List<RateVO> list_rate = getRatesByTimeRange(schemaNo, start_time, end_time);

			float total = 0;
			for (int i = 0; i < list_rate.size(); i++)
				total += list_rate.get(i).rate;
			total = total * chargingAmount / list_rate.size();
			log.info(total);
			return new BigDecimal(total).setScale(2, BigDecimal.ROUND_HALF_UP);
		}
		else
			return default_fee;
	}

	public BigDecimal computeChargingFee(Integer schemaNo, Date start_time, Date end_time, float[] chargingAmounts, List<PaymentDetail> paymentDetails)
	{

		log.info(schemaNo);
		log.info(start_time);
		log.info(end_time);
		log.info(chargingAmounts);

		if (chargingAmounts == null || chargingAmounts.length == 0)
			return default_fee;

		List<OmsChargerRate> list_vo = omsChargerRateDao.findListByCondition(new OmsChargerRate(), String.format(" WHERE rate_schema = %1$d ORDER BY start_time ", schemaNo));
		if (list_vo == null || list_vo.isEmpty())
			return default_fee;
		int mode = list_vo.get(0).getCharge_mode();
		if (mode == OmsConstant.PaymentMode.ByFrequency)
		{
			return list_vo.get(0).getRate().setScale(2, BigDecimal.ROUND_HALF_UP);
		}
		else if (mode == OmsConstant.PaymentMode.ByTime)
		{
			if (end_time.before(start_time))
				return BigDecimal.ZERO;

			List<RateVO> list_rate = getRatesByTimeRange(schemaNo, start_time, end_time);

			if (chargingAmounts.length != list_rate.size())
				throw new IllegalArgumentException(
						MessageFormat.format("Intersect between charging time and configured time in DB is {0}, but the No. of charging amount is {1}, which does not tally", list_rate.size(), chargingAmounts.length));

			float total = 0;
			for (int i = 0; i < list_rate.size(); i++)
			{
				float fee = list_rate.get(i).rate * chargingAmounts[i];
				total += fee;
				PaymentDetail detail = new PaymentDetail(fee, list_rate.get(i).rate, chargingAmounts[i], list_rate.get(i).start, list_rate.get(i).end, OmsConstant.PaymentMode.ByTime);
				paymentDetails.add(detail);
			}
			log.info(total);
			return new BigDecimal(total).setScale(2, BigDecimal.ROUND_HALF_UP);
		}
		else
			return default_fee;

	}

	public TimeInterval computeTimeIntersect(Date origin_starttime, Date origin_endtime, Date target_starttime, Date target_endtime)
	{

		Calendar o_start = convertWithoutDate(origin_starttime);
		Calendar o_end = convertWithoutDate(origin_endtime);
		Calendar t_start = convertWithoutDate(target_starttime);
		Calendar t_end = convertWithoutDate(target_endtime);

		if (o_end.before(o_start))
			return null;
		if (!t_end.after(o_start))
			return null;
		if (!o_end.after(t_start))
			return null;

		Date min = o_start.getTime();
		if (t_start.after(o_start))
			min = t_start.getTime();

		Date max = o_end.getTime();
		if (t_end.before(o_end))
			max = t_end.getTime();

		//		return (max.getTime() - min.getTime()) / (1000 * 60);
		return new TimeInterval(min, max);
	}

	private Calendar convertWithoutDate(Date date)
	{

		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.set(Calendar.YEAR, 2000);
		c.set(Calendar.MONTH, 1);
		c.set(Calendar.DAY_OF_MONTH, 1);
		return c;
	}

	private class TimeInterval
	{
		public TimeInterval(Date start, Date end)
		{
			this.start = start;
			this.end = end;
		}

		public Date start;
		public Date end;
	}

	private class RateVO
	{
		public RateVO(Float rate, Date start, Date end)
		{
			this.rate = rate;
			this.start = start;
			this.end = end;
		}

		public Float rate;
		public Date start;
		public Date end;
	}
}
